#include <linux/percpu.h>

static DEFINE_PER_CPU(struct cpuhp_cpu_state, cpuhp_state) = {
    .fail = CPUHP_INVALID,
};

struct cpuhp_cpu_state *per_cpuhp_cpu_state(unsigned int cpu)
{
    return per_cpu_ptr(&cpuhp_state, cpu);
}

static bool cpuhp_is_ap_state(enum cpuhp_state state)
{
	/*
	 * The extra check for CPUHP_TEARDOWN_CPU is only for documentation
	 * purposes as that state is handled explicitly in cpu_down.
	 */
	return state > CPUHP_BRINGUP_CPU && state != CPUHP_TEARDOWN_CPU;
}

static inline struct cpuhp_step *cpuhp_get_step(enum cpuhp_state state)
{
    return cpuhp_state_steps + state;
}

static inline bool cpuhp_step_empty(bool bringup, struct cpuhp_step *step)
{
    return bringup ? !step->startup.single : !step->teardown.single;
}

/* Sanity check for callbacks */
static inline int cpuhp_cb_check(enum cpuhp_state state)
{
    int ret = 0;

    if (state <= CPUHP_OFFLINE || state >= CPUHP_ONLINE)
        ret = -EINVAL;

    return ret;
}

static inline enum cpuhp_state cpuhp_set_state(int cpu, struct cpuhp_cpu_state *st, enum cpuhp_state target)
{
    enum cpuhp_state prev_state = st->state;
    bool bringup = st->state < target;

    st->rollback = false;
    st->last = NULL;

    st->target = target;
    st->single = false;
    st->bringup = bringup;

    return prev_state;
}

static inline void cpuhp_reset_state(int cpu, struct cpuhp_cpu_state *st,
                                     enum cpuhp_state prev_state)
{
    bool bringup = !st->bringup;

    st->target = prev_state;

    /*
     * Already rolling back. No need invert the bringup value or to change
     * the current state.
     */
    if (st->rollback)
        return;

    st->rollback = true;

    /*
     * If we have st->last we need to undo partial multi_instance of this
     * state first. Otherwise start undo at the previous state.
     */
    if (!st->last)
    {
        if (st->bringup)
            st->state--;
        else
            st->state++;
    }

    st->bringup = bringup;
}

/*
 * Hotplug state machine related functions
 */

/*
 * Get the next state to run. Empty ones will be skipped. Returns true if a
 * state must be run.
 *
 * st->state will be modified ahead of time, to match state_to_run, as if it
 * has already ran.
 */
static bool cpuhp_next_state(bool bringup,
                             enum cpuhp_state *state_to_run,
                             struct cpuhp_cpu_state *st,
                             enum cpuhp_state target)
{
    do
    {
        if (bringup)
        {
            if (st->state >= target)
                return false;

            *state_to_run = ++st->state;
        }
        else
        {
            if (st->state <= target)
                return false;

            *state_to_run = st->state--;
        }

        if (!cpuhp_step_empty(bringup, cpuhp_get_step(*state_to_run)))
            break;
    } while (true);

    return true;
}
